#!/bin/sh

PREREQ=""

prereqs()
{
	echo "$PREREQ"
}

case $1 in
prereqs)
	prereqs
	exit 0
	;;
esac

. /usr/share/initramfs-tools/hook-functions

# get_fs_devices() - determine source device(s) of mount point from /etc/fstab
#
# expected arguments:
# * fstab mount point (full path)
#
# This function searches for the first entry from /etc/fstab which has the
# given mountpoint set. It returns the canonical_device() of corresponding
# source device (first field in /etc/fstab entry).
# In case of btrfs, canoncial_device() of all btrfs source devices (possibly
# more than one) are returned.
#
get_fs_devices() {
	local device mount type options dump pass
	local wantmount="$1"

	if [ ! -r /etc/fstab ]; then
		return 1
	fi

	grep -s '^[^#]' /etc/fstab | \
	while read device mount type options dump pass; do
		if [ "$mount" = "$wantmount" ]; then
			local devices
			if [ "$type" = "btrfs" ]; then
				for dev in $(btrfs filesystem show $(canonical_device "$device" --no-simplify) 2>/dev/null | sed -r -e 's/.*devid .+ path (.+)/\1/;tx;d;:x') ; do
					devices="${devices:+$devices }$(canonical_device "$dev")"
				done
			else
				devices=$(canonical_device "$device") || return 0
			fi
			printf '%s' "$devices"
			return
		fi
	done
}

# get_resume_devices() - determine devices used for system suspend/hibernate
#
# expected arguments:
# * none
#
# This function searches well known places for devices that are used for system
# suspension and/or hibernation. It returns canonical_device() of any detected
# devices and prints a warning if more than one device is detected.
#
get_resume_devices() {
	local device opt count dupe candidates devices derived
	candidates=""

	# First, get a list of potential resume devices

	# uswsusp
	if [ -e /etc/uswsusp.conf ]; then
		device=$(sed -rn 's/^resume device[[:space:]]*[:=][[:space:]]*// p' /etc/uswsusp.conf)
		if [ -n "$device" ]; then
			candidates="${candidates:+$candidates }$device"
		fi
	fi

	# uswsusp - again...
	if [ -e /etc/suspend.conf ]; then
		device=$(sed -rn 's/^resume device[[:space:]]*[:=][[:space:]]*// p' /etc/suspend.conf)
		if [ -n "$device" ]; then
			candidates="${candidates:+$candidates }$device"
		fi
	fi

	# regular swsusp
	for opt in $(cat /proc/cmdline); do
		case $opt in
			resume=*)
				device="${opt#resume=}"
				candidates="${candidates:+$candidates }$device"
				;;
		esac
	done

	# initramfs-tools >=0.129
	device="${RESUME:-auto}"
	if [ "$device" != none ]; then
		if [ "$device" = auto ]; then
			# next line from /usr/share/initramfs-tools/hooks/resume
			device="$(grep ^/dev/ /proc/swaps | sort -rnk3 | head -n 1 | cut -d " " -f 1)"
			if [ -n "$device" ]; then
				device="UUID=$(blkid -s UUID -o value "$device" || true)"
			fi
		fi
		candidates="${candidates:+$candidates }$device"
	fi

	# Now check the sanity of all candidates
	devices=""
	count=0
	for device in $candidates; do
		# Remove quotes around device candidate
		device=$(printf '%s' "$device" | sed -r -e 's/^"(.*)"\s*$/\1/' -e "s/^'(.*)'\s*$/\1/")

		# Weed out clever defaults
		if [ "$device" = "<path_to_resume_device_file>" ]; then
			continue
		fi

		# Detect devices required by decrypt_derived
		derived=$(get_derived_device "$device")
		if [ -n "$derived" ]; then
			devices="${devices:+$devices }$derived"
		fi

		device=$(canonical_device "$device") || return 0

		# Weed out duplicates
		dupe=0
		for opt in $devices; do
			if [ "$device" = "$opt" ]; then
				dupe=1
			fi
		done
		if [ $dupe -eq 1 ]; then
			continue
		fi

		# This device seems ok
		devices="${devices:+$devices }$device"
		count=$(( $count + 1 ))
	done

	if [ $count -gt 1 ]; then
		echo "cryptsetup: WARNING: found more than one resume device candidate:" >&2
		for device in $devices; do
			echo "                     $device" >&2
		done
	fi

	if [ $count -gt 0 ]; then
		printf '%s' "$devices"
	fi

	return 0
}

# get_initramfs_devices() - determine devices with explicit 'initramfs' option
#
# expected arguments:
# * none
#
# This function processes entries from /etc/crypttab with the 'initramfs'
# option set. For each processed device, potential get_derived_device()
# devices are determined. The canonical_device() of each detected device
# is returned.
#
get_initramfs_devices() {
	local device opt count dupe target source key options candidates devices derived

	candidates="$(grep -s '^[^#]' /etc/crypttab | \
	while read target source key options; do
		if printf '%s' "$options" | grep -Eq "^(.*,)?initramfs(,.*)?$"; then
			echo " /dev/mapper/$target"
		fi
	done;)"

	devices=""
	count=0
	for device in $candidates; do
		# Detect devices required by decrypt_derived
		derived=$(get_derived_device "$device")
		if [ -n "$derived" ]; then
			devices="${devices:+$devices }$derived"
		fi

		device=$(canonical_device "$device") || return 0

		# Weed out duplicates
		dupe=0
		for opt in $devices; do
			if [ "$device" = "$opt" ]; then
				dupe=1
			fi
		done
		if [ $dupe -eq 1 ]; then
			continue
		fi

		# This device seems ok
		devices="${devices:+$devices }$device"
		count=$(( $count + 1 ))
	done

	if [ $count -gt 0 ]; then
		printf '%s' "$devices"
	fi

	return 0
}

# get_derived_device() - determine dependency devices for decrypt_derived
#
# expected arguments:
# * crypttab target device name (either <name> or /dev/mapper/<name>)
#
# This function takes a target device name and checks whether this device has
# the decrypt_derived keyscript set in /etc/crypttab. If true, the dependency
# device required for the decrypt_derived keyscript is detected and its
# canonical_device() returned if it's not listed in $rootdevs.
#
get_derived_device() {
	local device derived
	device="$1"

	derived="$( awk -vtarget="${device#/dev/mapper/}" \
		'$1 == target && $4 ~ /^(.*,)?keyscript=([^,]*\/)?decrypt_derived(,.*)?$/ {print $3; exit}' \
		/etc/crypttab )"
	if [ -n "$derived" ]; then
		if node_is_in_crypttab "$derived"; then
			derived=$(canonical_device "/dev/mapper/$derived") || return 0
			if ! printf '%s' "$rootdevs" | tr ' ' '\n' | grep -Fxq "$derived"; then
				printf '%s' "$derived"
			fi
		else
			echo "cryptsetup: WARNING: decrypt_derived device $derived not found in crypttab" >&2
		fi
	fi
}

# node_is_in_crypttab() - test whether a device is configured in /etc/crypttab
#
# expected arguments:
# * crypttab target device names (without /dev/mapper/ prefix)
#
# This function takes a target device name and fails if it is not
# configured in /etc/crypttab.
#
node_is_in_crypttab() {
	[ -f /etc/crypttab ] || return 1
	sed -n '/^[^#]/ s/\s.*//p' /etc/crypttab | grep -Fxq "$1"
}

# node_or_pv_is_in_crypttab() - test whether devices are configured in /etc/crypttab
#
# expected arguments:
# * crypttab target device names (without /dev/mapper/ prefix), or LVM
#   logical volume device-mapper name (format <VG>-<LV>)
#
# This function fails unless every argument is either a target device
# name configured in /etc/crypttab, or an LVM logical volume
# device-mapper name (format <VG>-<LV>) with only parents devices (PVs)
# configured in /etc/crypttab.
#
node_or_pv_is_in_crypttab() {
	local node lvmnodes lvmnode
	for node in "$@"; do
		if ! node_is_in_crypttab "$node"; then
			lvmnodes="$(get_lvm_deps "$node" --assert-crypt)" || return 1
			[ "$lvmnodes" ] || return 1
			for lvmnode in $lvmnodes; do
				node_is_in_crypttab "$lvmnode" || return 1
			done
		fi
	done
	return 0
}

# get_lvm_deps() - determine the parent devices (PVs) of a LVM logical volume
#
# expected arguments:
# * LVM logical volume device-mapper name (format <VG>-<LV>)
# * optional options to the function
#
# This function takes a LVM logical volume name and determines the corresponding
# crypted physical volumes (PVs). It returns the name of the underlying
# device-mapper crypt devices (without /dev/mapper).
# If option '--assert-crypt' is given as second argument, then the
# function fails unless all PVs are dm-crypt devices.
#
get_lvm_deps() {
	local node opt deps maj min depnode
	node="$1"
	opt="${2:-}"

	if [ -z "$node" ]; then
		echo "cryptsetup: WARNING: get_lvm_deps - invalid arguments" >&2
		return 1
	fi

	if ! deps=$(vgs --noheadings -o pv_name $(dmsetup --noheadings splitname $node | cut -d':' -f1) 2>/dev/null); then
		# $node is not a LVM node, stopping here
		[ "$opt" != '--assert-crypt' ] && return 0 || return 1
	fi

	# We should now have a list of physical volumes for the VG
	for dep in $deps; do
		depnode=$(dmsetup info -c --noheadings -o name "$dep" 2>/dev/null)
		if [ -z "$depnode" ]; then
			[ "$opt" != '--assert-crypt' ] && continue || return 1
		fi
		if [ "$(dmsetup table "$depnode" 2>/dev/null | cut -d' ' -f3)" != "crypt" ]; then
			get_lvm_deps "$depnode" $opt || return 1
			continue
		fi
		printf '%s\n' "$depnode"
	done

	return 0
}

# get_device_opts() - determine and set options for a crypttab target device
#
# expected arguments:
# * crypttab target device name (without /dev/mapper/ prefix)
# * optional extra options
#
# This function determines options for a crypttab target device and sets them
# accordingly. In order to detect the options, it parses the corresponding
# /etc/crypttab entry and takes optional extra options as second argument.
# Some sanity checks are done on the corresponding source device and configured
# options.
# After everything is processed, the options are saved in '$OPTIONS' for later
# access by parent functions.
#
get_device_opts() {
	local target source link extraopts rootopts opt key
	target="$1"
	extraopts="$2"
	KEYSCRIPT=""
	KEYFILE="" # key file to copy to the initramfs image
	CRYPTHEADER=""
	OPTIONS=""

	if [ -z "$target" ]; then
		echo "cryptsetup: WARNING: get_device_opts - invalid arguments" >&2
		return 1
	fi

	opt="$( awk -vtarget="$target" '$1 == target {gsub(/[ \t]+/," "); print; exit}' /etc/crypttab )"
	source=$( printf '%s' "$opt" | cut -d " " -f2 )
	key=$( printf '%s' "$opt" | cut -d " " -f3 )
	rootopts=$( printf '%s' "$opt" | cut -d " " -f4- )

	if [ -z "$opt" ] || [ -z "$source" ] || [ -z "$key" ] || [ -z "$rootopts" ]; then
		echo "cryptsetup: WARNING: invalid line in /etc/crypttab for $target - $opt" >&2
		return 1
	fi

	# Sanity checks for $source
	if [ -h "$source" ]; then
		link=$(readlink -nqe "$source")
		if [ -z "$link" ]; then
			echo "cryptsetup: WARNING: $source is a dangling symlink" >&2
			return 1
		fi

		if [ "$link" != "${link#/dev/mapper/}" ]; then
			echo "cryptsetup: NOTE: using $link instead of $source for $target" >&2
			source="$link"
		fi
	fi

	if [ "UUID=${source#UUID=}" = "$source" -a ! -b "/dev/disk/by-uuid/${source#UUID=}" ] || [ "UUID=${source#UUID=}" != "$source" -a ! -b "$source" ]; then
		echo "cryptsetup: WARNING: Invalid source device $source" >&2
	fi

	# Sanity checks for $key
	if [ "$key" = "/dev/random" ] || [ "$key" = "/dev/urandom" ]; then
		echo "cryptsetup: WARNING: target $target has a random key, skipped" >&2
		return 1
	fi

	if [ -n "$extraopts" ]; then
		rootopts="$extraopts,$rootopts"
	fi

	# We have all the basic options, let's go trough them
	OPTIONS="target=$target,source=$source"
	local IFS_BCK="$IFS"
	local IFS=", "
	unset HASH_FOUND
	unset LUKS_FOUND
	for opt in $rootopts; do
		case $opt in
			cipher=*)
				OPTIONS="$OPTIONS,$opt"
				;;
			size=*)
				OPTIONS="$OPTIONS,$opt"
				;;
			hash=*)
				OPTIONS="$OPTIONS,$opt"
				HASH_FOUND=1
				;;
			tries=*)
				OPTIONS="$OPTIONS,$opt"
				;;
			discard)
				OPTIONS="$OPTIONS,$opt"
				;;
			luks)
				LUKS_FOUND=1
				;;
			header=*)
				opt="${opt#header=}"
				if [ ! -e "$opt" ]; then
					echo "cryptsetup: WARNING: target $target has an invalid header, skipped" >&2
					return 1
				fi
				CRYPTHEADER="$opt"
				OPTIONS="$OPTIONS,header=$CRYPTHEADER"
				;;
			tcrypt)
				OPTIONS="$OPTIONS,$opt"
				;;
			keyscript=*)
				opt="${opt#keyscript=}"
				if [ ! -x "/lib/cryptsetup/scripts/$opt" ] && [ ! -x "$opt" ]; then
					echo "cryptsetup: WARNING: target $target has an invalid keyscript, skipped" >&2
					return 1
				fi
				KEYSCRIPT="$opt"
				OPTIONS="$OPTIONS,keyscript=/lib/cryptsetup/scripts/$(basename "$opt")"
				;;
			keyslot=*)
				OPTIONS="$OPTIONS,$opt"
				;;
			veracrypt)
				OPTIONS="$OPTIONS,$opt"
				;;
			lvm=*)
				OPTIONS="$OPTIONS,$opt"
				;;
			rootdev)
				OPTIONS="$OPTIONS,$opt"
				;;
			resumedev)
				OPTIONS="$OPTIONS,$opt"
				;;
			*)
				# Presumably a non-supported option
				;;
		esac
	done
	IFS="$IFS_BCK"

	# Warn for missing hash option, unless we have a LUKS partition
	if [ -z "$HASH_FOUND" ] && [ -z "$LUKS_FOUND" ]; then
		echo "WARNING: Option hash missing in crypttab for target $target, assuming ripemd160." >&2
		echo "         If this is wrong, this initramfs image will not boot." >&2
		echo "         Please read /usr/share/doc/cryptsetup/README.initramfs.gz and add" >&2
		echo "         the correct hash option to your /etc/crypttab."  >&2
	fi

	# Warn that header only applies to a LUKS partition currently
	if [ -n "$CRYPTHEADER" ] && [ -z "$LUKS_FOUND" ]; then
		echo "WARNING: Option LUKS missing in crypttab for target $target." >&2
		echo "         Headers are only supported for LUKS devices." >&2
	fi

	# If keyscript is set, the "key" is just an argument to the script
	if [ "$key" != "none" ] && [ -z "$KEYSCRIPT" ]; then
		case "$key" in
			$KEYFILE_PATTERN)
				KEYFILE="$key"
				key="/cryptroot-keyfiles/${target}.key"
				;;
			*)
				key=$(readlink -e "$key")
				# test whether $target is a root device (or parent of the root device)
				if printf '%s' "$OPTIONS" | grep -Eq '^(.*,)?rootdev(,.*)?$'; then
					echo "cryptsetup: WARNING: root target $target uses a key file, skipped" >&2
					return 1
				# test whether a) key file is not on root fs
				#           or b) root fs is not encrypted
				elif [ "$(stat -c %m -- "$key" 2>/dev/null)" != / ] || ! node_or_pv_is_in_crypttab $rootdevs; then
					echo "cryptsetup: WARNING: $target's key file $key is not on an encrypted root FS, skipped" >&2
					return 1
				fi
				if printf '%s' "$OPTIONS" | grep -Eq '^(.*,)?resumedev(,.*)?$'; then
					# we'll be able to decrypt the device, but won't be able to use it for resuming
					echo "cryptsetup: WARNING: resume device $source uses a key file" >&2
				fi
				# prepend "/root" (to be substituted by the real root FS
				# mountpoint "$rootmnt" in the boot script) to the
				# absolute filename
				key="/root$key"
				;;
		esac
		OPTIONS="$OPTIONS,keyscript=cat"
	fi
	OPTIONS="$OPTIONS,key=$key"
}

# get_device_modules() - determine required crypto kernel modules for device
#
# expected arguments:
# * crypttab target device name (without /dev/mapper/ prefix)
#
# This function determines the required crypto kernel modules for cipher,
# block cipher and optionally ivhash of the target device and returns them.
#
get_device_modules() {
	local node value cipher blockcipher ivhash
	node="$1"

	# Check the ciphers used by the active root mapping
	value=$(dmsetup table "$node" | cut -d " " -f4)
	cipher=$(echo "$value" | cut -d ":" -f1 | cut -d "-" -f1)
	blockcipher=$(echo "$value" | cut -d ":" -f1 | cut -d "-" -f2)
	ivhash=$(echo "$value" | cut -d ":" -s -f2)

	if [ -n "$cipher" ]; then
		echo "$cipher"
	else
		return 1
	fi

	if [ -n "$blockcipher" ] && [ "$blockcipher" != "plain" ]; then
		echo "$blockcipher"
	fi

	if [ -n "$ivhash" ] && [ "$ivhash" != "plain" ]; then
		echo "$ivhash"
	fi
	return 0
}

# canonical_device() - determine the 
#
# expected arguments:
# * device (either full path or LABEL=<x> or UUID=<y>)
# * optional options to the function
#
# This function takes a device as argument and determines the corresponding
# canonical device name.
# If option '--no-simplify' is given as second argument, then the origin device
# path after unraveling LABEL= and UUID= format and following symlinks is
# returned.
# If no option is given, the device is further unraveled and depending on the
# device path, either the corresponding device-mapper path (as found in
# /dev/mapper/) or the the corresponding disk symlink (as found in
# /dev/disk/by-*/) is returned.
#
canonical_device() {
	local dev altdev original
	dev="$1"
	opt="$2"

	if [ "${dev#LABEL=}" != "$dev" ]; then
		altdev="${dev#LABEL=}"
		dev="/dev/disk/by-label/$(printf '%s' "$altdev" | sed 's,/,\\x2f,g')"
	elif [ "${dev#UUID=}" != "$dev" ]; then
		altdev="${dev#UUID=}"
		dev="/dev/disk/by-uuid/$altdev"
	fi

	original="$dev"
	if [ -h "$dev" ]; then
		dev=$(readlink -e "$dev")
	fi

	if [ "$opt" = "--no-simplify" ]; then
		printf '%s' "$dev"
		return 0
	fi

	if [ "x${dev%/dev/dm-*}" = "x" ]; then
		# try to detect corresponding symlink in /dev/mapper/
		for dmdev in /dev/mapper/*; do
			if [ "$(readlink -e "$dmdev")" = "$dev" ]; then
				dev="$dmdev"
			fi
		done
	fi

	altdev="${dev#/dev/mapper/}"
	if [ "$altdev" != "$dev" ]; then
		printf '%s' "$altdev"
		return 0
	elif [ "x${original%/dev/disk/by-*/*}" = "x" ]; then
		# support crypttab UUID/LABEL entries
		# this is a /dev/disk/by-*/ path so return just the 'basename'
		echo "${original##/dev/disk/by-*/}"
		return 0
	fi

	echo "cryptsetup: WARNING: failed to detect canonical device of $original" | systemd-cat -t cryptsetup -p info
	return 1
}

# add_device() - Process a given device and add to /conf/conf.d/cryptroot
#
# expected arguments:
# * device name (either crypttab target device name without /dev/mapper/ prefix
#                    or LVM device-mapper name in format '<VG>:<LV>')
#
# This function takes a device name, does all required processing and adds the
# result with all device options to /conf/conf.d/cryptroot in the initramfs.
# Additionally, it returns required kernel modules.
#
add_device() {
	local node nodes lvmnodes opts lastopts i count
	nodes="$1"
	opts=""     # Applied to all nodes
	lastopts="" # Applied to last node

	if [ -z "$nodes" ]; then
		return 0
	fi

	# Flag root and resume devices
	if printf '%s' "$rootdevs" | tr ' ' '\n' | grep -Fxq "$nodes"; then
		opts="${opts:+$opts,}rootdev"
	fi
	if printf '%s' "$resumedevs" | tr ' ' '\n' | grep -Fxq "$nodes"; then
		opts="${opts:+$opts,}resumedev"
	fi

	# Check that it is a node under /dev/mapper/
	# nodes=$(canonical_device "$nodes") || return 0

	# Can we find this node in crypttab
	if ! node_is_in_crypttab "$nodes"; then
		# dm node but not in crypttab, is it a lvm device backed by dm-crypt nodes?
		lvmnodes=$(get_lvm_deps "$nodes") || return 1

		# not backed by any dm-crypt nodes; stop here
		if [ -z "$lvmnodes" ]; then
		    return 0
		fi

		# It is a lvm device!
		opts="${opts:+$opts,}lvm=$nodes"
		nodes="$lvmnodes"
	fi

	# Prepare to setup each node
	count=$(printf '%s' "$nodes" | wc -w)
	i=1
	for node in $nodes; do
		# Prepare the additional options
		if [ $i -eq $count ]; then
			if [ -n "$lastopts" ]; then
				opts="${opts:+$opts,}$lastopts"
			fi
		fi

		# Get crypttab root options
		if ! get_device_opts "$node" "$opts"; then
			continue
		fi
		printf '%s\n' "$OPTIONS" >>"$DESTDIR/conf/conf.d/cryptroot"

		# If we have a keyscript, make sure it is included
		if [ -n "$KEYSCRIPT" ]; then
			if [ ! -d "$DESTDIR/lib/cryptsetup/scripts" ]; then
				mkdir -p "$DESTDIR/lib/cryptsetup/scripts"
			fi

			if [ -e "/lib/cryptsetup/scripts/$KEYSCRIPT" ]; then
				copy_exec "/lib/cryptsetup/scripts/$KEYSCRIPT" /lib/cryptsetup/scripts >&2
			elif [ -e "$KEYSCRIPT" ]; then
				copy_exec "$KEYSCRIPT" /lib/cryptsetup/scripts >&2
			elif KSTYPE="$(type "$KEYSCRIPT" 2>&1)"; then
				if [ -x "${KSTYPE#"$KEYSCRIPT" is }" ]; then
					copy_exec "${KSTYPE#"$KEYSCRIPT" is }" /lib/cryptsetup/scripts >&2
				fi
			else
				echo "cryptsetup: WARNING: failed to find keyscript $KEYSCRIPT" >&2
				continue
			fi
		elif [ -n "$KEYFILE" ]; then
			case "$KEYFILE" in
				$KEYFILE_PATTERN)
					mkdir -pm0700 "$DESTDIR/cryptroot-keyfiles"
					cp --preserve=all "$KEYFILE" "$DESTDIR/cryptroot-keyfiles/${node}.key"
					;;
			esac
		fi

		# If we have a LUKS header, make sure it is included
		# TODO: make it configurable to include the LUKS header into initramfs
		# disabled for now due to security reasons
		if [ -n "$CRYPTHEADER" ]; then
			if [ ! -d "$DESTDIR/conf/conf.d/cryptheader" ]; then
				mkdir -p "$DESTDIR/conf/conf.d/cryptheader"
			fi

			#if [ -e "$CONFDIR/conf.d/cryptheader/$CRYPTHEADER" ]; then
			#	copy_exec "$CONFDIR/conf.d/cryptheader/$CRYPTHEADER" /conf/conf.d/cryptheader >&2
			#elif [ -e "$CRYPTHEADER" ]; then
			#	copy_exec "$CRYPTHEADER" /conf/conf.d/cryptheader >&2
			#else
			#	echo "cryptsetup: WARNING: failed to find LUKS header $CRYPTHEADER" >&2
			#	continue
			#fi
		fi
		

		# Calculate needed modules
		modules=$(get_device_modules $node | sort | uniq)
		if [ -z "$modules" ]; then
			echo "cryptsetup: WARNING: failed to determine cipher modules to load for $node" >&2
			continue
		fi
		echo dm_mod
		echo dm_crypt
		echo "$modules"
		# Load hardware aes module
		if cpu_has_aesni; then
			echo aesni
		fi
		i=$(( $i + 1 ))
	done

	return 0
}

# cpu_has_aesni() - Detect whether the host CPU has AES-NI support
#
# expected arguments:
# * none
#
# This functions returns true when the host CPU has AES-NI support.
#
cpu_has_aesni() {
	return $(grep -q "^flags\s*:\s*.*aes" /proc/cpuinfo)
}

# add_crypto_modules() - determine kernel module path and add to initramfs
#
# expected arguments:
# * kernel module name
#
# This function takes a kernel module name, determines the corresponding path
# and runs manual_add_modules() from initramfs hook functions to add the module
# to the initramfs.
#
add_crypto_modules() {
	local mod file altmod found genericfound
	mod="$1"
	found=""
	genericfound=""

	if [ -z "$mod" ]; then
		return 1
	fi

	# We have several potential sources of modules (in order of preference):
	#
	#   a) /lib/modules/$VERSION/kernel/arch/$ARCH/crypto/$mod-$specific.ko
	#   b) /lib/modules/$VERSION/kernel/crypto/$mod_generic.ko
	#   c) /lib/modules/$VERSION/kernel/crypto/$mod.ko
	#
	# and (currently ignored):
	#
	#   d) /lib/modules/$VERSION/kernel/drivers/crypto/$specific-$mod.ko

	for file in $(find "$MODULESDIR/kernel/arch/" -name "$mod-*.ko" 2>/dev/null); do
		altmod="${file##*/}"
		altmod="${altmod%.ko}"
		manual_add_modules "$altmod"
		found="yes"
	done

	for file in $(find "$MODULESDIR/kernel/crypto/" -name "${mod}_generic.ko" 2>/dev/null); do
		altmod="${file##*/}"
		altmod="${altmod%.ko}"
		manual_add_modules "$altmod"
		found="yes"
		genericfound="yes"
	done

	if [ -z "$genericfound" ]; then
		for file in $(find "$MODULESDIR/kernel/crypto/" -name "${mod}.ko" 2>/dev/null); do
			altmod="${file##*/}"
			altmod="${altmod%.ko}"
			manual_add_modules "$altmod"
			found="yes"
		done
	fi

	if [ -z "$found" ]; then
		return 1
	fi

	return 0
}

#
# Begin real processing
#

setup="no"
rootdevs=""
usrdevs=""
resumedevs=""

# XXX Backward compatibility: remove once Stretch has been promoted stable
#for v in CRYPTSETUP KEYFILE_PATTERN; do
#	if eval [ "\${$v+x}" ]; then
#		echo "WARNING: Setting $v in /etc/initramfs-tools/initramfs.conf" \
#		     "is deprecated and will stop working in the future." \
#		     "Use /etc/cryptsetup-initramfs/conf-hook instead." >&2
#	fi
#done

# Load the hook's config
if [ -f "/etc/cryptsetup-initramfs/conf-hook" ]; then
	. /etc/cryptsetup-initramfs/conf-hook
fi

# Include cryptsetup modules, regardless of _this_ machine configuration
if [ -n "$CRYPTSETUP" ] && [ "$CRYPTSETUP" != "n" ]; then
	setup="yes"
fi

if [ "$KEYFILE_PATTERN" ]; then
	setup="yes"
	case "${UMASK:-$(umask)}" in
		0[0-7]77) ;;
		*) echo "WARNING: permissive UMASK (${UMASK:-$(umask)})." \
		        "Private key material inside the initrd might be left unprotected." >&2
		;;
	esac
fi

# Find the root and resume device(s)
if [ -r /etc/crypttab ]; then
	rootdevs=$(get_fs_devices /)
	if [ -z "$rootdevs" ]; then
		echo "cryptsetup: WARNING: could not determine root device from /etc/fstab" | systemd-cat -t cryptsetup -p info
	fi
	usrdevs=$(get_fs_devices /usr)
	resumedevs=$(get_resume_devices)
	initramfsdevs=$(get_initramfs_devices)
fi

# Load the config opts and modules for each device
for dev in $rootdevs $usrdevs $resumedevs $initramfsdevs; do
	if ! modules=$(add_device "$dev"); then
		echo "cryptsetup: FAILURE: could not determine configuration for $dev" >&2
		continue
	fi

	if [ -n "$modules" ]; then
		setup="yes"
	fi

	if [ "$setup" = "no" ]; then
		continue
	fi

	if [ "$MODULES" = "most" ]; then
		archcrypto="$(find "$MODULESDIR/kernel/arch" -type d -name "crypto" 2>/dev/null)"
		if [ -n "$archcrypto" ]; then
			copy_modules_dir "${archcrypto##*${MODULESDIR}/}"
		fi
		copy_modules_dir "kernel/crypto"
	else
		for mod in $modules; do
			add_crypto_modules $mod
		done
	fi
done

# With large initramfs, we always add a basic subset of modules
if [ "$MODULES" != "dep" ] && [ "$setup" = "yes" ]; then
	for mod in aes cbc chainiv cryptomgr krng sha256 xts; do
		add_crypto_modules $mod
	done
fi

# See if we need to add the basic components
if [ "$setup" = "yes" ]; then
	for mod in dm_mod dm_crypt; do
		manual_add_modules $mod
	done

	copy_exec /sbin/cryptsetup
	copy_exec /sbin/dmsetup
	copy_exec /lib/cryptsetup/askpass

	# We need sed. Either via busybox or as standalone binary.
	if [ "$BUSYBOX" = "n" ] || [ ! -e ${BUSYBOXDIR}/busybox ]; then
		copy_exec /bin/sed
	fi
fi

exit 0
